﻿/*
#-------------------------------------------------------------------------------
# Name:        LicensePlateRecognition - Applies the LPR algorithms on the image
#
# Usage:       Need the List of VisionAlgorithms in the order seen in the constructor
#              Needs the Backgroundworker from the mainForm in order to pass on the logs and bitmaps
#               
#
# Requires:    Visual Studio 2010 and up.
#
# Author:      Roy Scheefhals, Duncan Jenkins and Martijn Koopman
#
 *              Duncan Jenkins made the Segmentation and OCR implementation
 *              Martijn Koopman and Roy Scheefhals the rest
#
# Created:     30-03-2012
#-------------------------------------------------------------------------------
*/


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.ComponentModel;
using LibraryCS;

namespace Eindproject {
    public class LicensePlateRecognition {

        BackgroundWorker backgroundWorker;
        List<VisionAlgorithm> VisionAlgorithms;
        bool verboseMode;
        List<string> foundLPNs = new List<string>();

        MedianExecute Median;
        HSVColorSegmentation HSVSegmentation;
        Binarize binarize;
        NonRecursiveGrowingSelection blobdet;
        SegmentationAlgorithm NumberSegment;
        OCRAlgorithm OCR;


        public LicensePlateRecognition(List<VisionAlgorithm> VisionAlgorithms, BackgroundWorker backgroundWorker, bool verbose) {
            this.VisionAlgorithms = VisionAlgorithms;
            this.backgroundWorker = backgroundWorker;
            this.verboseMode = verbose;

            //Extract all the Algorithms
            Median = (MedianExecute)VisionAlgorithms[0];
            HSVSegmentation = (HSVColorSegmentation)VisionAlgorithms[1];
            binarize = (Binarize)VisionAlgorithms[2];
            blobdet = (NonRecursiveGrowingSelection)VisionAlgorithms[3];
            NumberSegment = (SegmentationAlgorithm)VisionAlgorithms[4];
            OCR = (OCRAlgorithm)VisionAlgorithms[5];
        }

        /* *************************************************************** */
        /* ********** Execute Licecense Plate Recognition (LPR) ********** */
        /* *************************************************************** */
        public List<LicensePlate> doLPR(Bitmap inputBitmap) {


            List<LicensePlate> PossibleLicensePlates = new List<LicensePlate>();
            List<LicensePlate> FinalLicensePlates = new List<LicensePlate>();
            
            foundLPNs.Clear();
            VImage original = new VImage(inputBitmap);
            //*****************************//
            //********* Scaling ************//
            int oldWidth = inputBitmap.Width;
            int oldHeight = inputBitmap.Height;
            int numInputPixels = oldWidth * oldHeight;
            int numScaledPixels = 800 * 600;
            double widthProportion, heightProportion, widthSizeFactor, heightSizeFactor;
            if (oldWidth > oldHeight) {
                heightProportion = 1.0;
                widthProportion = (double)oldWidth / (double)oldHeight;
            } else if (oldHeight > oldWidth) {
                heightProportion = (double)oldHeight / (double)oldWidth;
                widthProportion = 1.0;
            } else {
                heightProportion = 1.0;
                widthProportion = 1.0;
            }

            double x = Math.Sqrt((double)numScaledPixels / (widthProportion * heightProportion));

            int newWidth = (int)(x * widthProportion);
            int newHeight = (int)(x * heightProportion);
            widthSizeFactor = (double)oldWidth / (double)newWidth;
            heightSizeFactor = (double)oldHeight / (double)newHeight;

            Bitmap scaledBitmap = new Bitmap(newWidth, newHeight);

            using (Graphics g = Graphics.FromImage((Image)scaledBitmap))
                g.DrawImage(inputBitmap, 0, 0, newWidth, newHeight);

            if (verboseMode) {
                log("Scaled from " + oldWidth + "; " + oldHeight + " to " + newWidth + "; " + newHeight);
                outputBitmap(scaledBitmap, "Down scaled");
            }
            VImage scaledImage = new VImage(scaledBitmap);
            //*****************************//
            //********* Median ************//
            VImage medianImage = Median.ExecuteMedian(scaledImage);
            if (verboseMode) {
                if (Median.hasDoneGray()) {
                    outputBitmap(medianImage.returnNewBitmapOutput(medianImage.GrayPixels), "Median gray");
                } else {
                    outputBitmap(medianImage.returnNewBitmapColour(medianImage.RedPixels,
                                                                    medianImage.GreenPixels,
                                                                    medianImage.BluePixels), "Median color");
                }
            }

            //*****************************//
            //***** HSV Segmentation ******//
            VImage AfterSegmentation = HSVSegmentation.SegmentateImage(medianImage);
            if (verboseMode) {
                outputBitmap(AfterSegmentation.returnNewBitmapColour(AfterSegmentation.RedPixels,
                                                                    AfterSegmentation.GreenPixels,
                                                                    AfterSegmentation.BluePixels), "HSV segmentation");

            }

            //*****************************//
            //********* Binarize **********//
            VImage AfterBinarize = binarize.BinarizeImage(AfterSegmentation);
            if (verboseMode) {
                outputBitmap(AfterBinarize.returnNewBitmapColour(AfterBinarize.RedPixels,
                                                                AfterBinarize.GreenPixels,
                                                                AfterBinarize.BluePixels),
                             "Binarized");
            }

            //**********************************//
            //********* Blobdetection **********//
            List<Blob> blobList = blobdet.DetectBlobs(AfterBinarize);
            List<Blob> filteredBlobList = blobdet.filterBlobs(blobList);    // Filter blobs on size

            if (verboseMode)
                outputBitmap(blobdet.drawBoundingBox(filteredBlobList), "Selected blobs");
            
            //**********************************//
            //******* Crop Candidate LP ********//
            //****** FROM ORIGINAL IMAGE *********//
            List<VImage> candidateLPImages = new List<VImage>();

            foreach (Blob b in filteredBlobList) {

                // put coordinates into licenseplate object as 'Detected licenseplate points'
                LicensePlate LP = new LicensePlate();
                LP.topleft = new Point((int)(b.topLeft.X * widthSizeFactor), (int)(b.topLeft.Y * heightSizeFactor));
                LP.topright = new Point((int)(b.topRight.X * widthSizeFactor), (int)(b.topRight.Y * heightSizeFactor));
                LP.bottomleft = new Point((int)(b.bottomLeft.X * widthSizeFactor), (int)(b.bottomLeft.Y * heightSizeFactor));
                LP.bottomright = new Point((int)(b.bottomRight.X * widthSizeFactor),(int) (b.bottomRight.Y * heightSizeFactor));
                PossibleLicensePlates.Add(LP);

                //Crop the blob out of the original image ( first: scale bounding box coordinates back to original image )
                b.minx = (int)((b.minx + 1) * widthSizeFactor);
                b.maxx = (int)((b.maxx - 1) * widthSizeFactor);
                b.miny = (int)((b.miny + 1) * heightSizeFactor);
                b.maxy = (int)((b.maxy - 1) * heightSizeFactor);
                VImage croppedblob = original.BlobCrop(b.minx, b.miny, b.maxx, b.maxy);

                //Scale BlobCoordinates back to the original image and give them the translation to the new image
                b.bottomLeft.X = (int)(b.bottomLeft.X * widthSizeFactor) - b.minx;
                b.bottomLeft.Y = (int)(b.bottomLeft.Y * heightSizeFactor) - b.miny;
                b.topLeft.X = (int)(b.topLeft.X * widthSizeFactor) - b.minx;
                b.topLeft.Y = (int)(b.topLeft.Y * heightSizeFactor) - b.miny;
                b.bottomRight.X = (int)(b.bottomRight.X * widthSizeFactor) - b.minx;
                b.bottomRight.Y = (int)(b.bottomRight.Y * heightSizeFactor) - b.miny;
                b.topRight.X = (int)(b.topRight.X * widthSizeFactor) - b.minx;
                b.topRight.Y = (int)(b.topRight.Y * heightSizeFactor) - b.miny;

                

                

                // calculate new bounding box
                b.minx = b.miny = 0;
                b.maxx = croppedblob.Width;
                b.maxy = croppedblob.Height;

                croppedblob.blob = b;
                if (verboseMode) {
                    outputBitmap(croppedblob.getBitmap(), "Candidate LP");
                }
                candidateLPImages.Add(croppedblob);
            }

            //******************************************//
            //********* Rotate license plates **********//
            List<VImage> rotatedLPImages = new List<VImage>();

            foreach (VImage candidateLPImage in candidateLPImages) {
                VImage rotatedLPImage = RotatePlate.rotate(candidateLPImage);

                rotatedLPImages.Add(rotatedLPImage);
                if (verboseMode) {
                    outputBitmap(rotatedLPImage.getBitmap(), "Rotated LP");

                    // Draw image with corners
                    Bitmap drawnBitmap = rotatedLPImage.getBitmap();
                    Graphics g = Graphics.FromImage(drawnBitmap);
                    g.DrawRectangle(Pens.Red, new Rectangle(rotatedLPImage.blob.minx,
                        rotatedLPImage.blob.miny,
                        rotatedLPImage.blob.maxx - rotatedLPImage.blob.minx,
                        rotatedLPImage.blob.maxy - rotatedLPImage.blob.miny));

                    Point topLeftCorner = new Point(rotatedLPImage.blob.topLeft.X, rotatedLPImage.blob.topLeft.Y);
                    Point bottomLeftCorner = new Point(rotatedLPImage.blob.bottomLeft.X, rotatedLPImage.blob.bottomLeft.Y);
                    Point topRightCorner = new Point(rotatedLPImage.blob.topRight.X, rotatedLPImage.blob.topRight.Y);
                    Point bottomRightCorner = new Point(rotatedLPImage.blob.bottomRight.X, rotatedLPImage.blob.bottomRight.Y);

                    //Draw the lines between the corners of the blob
                    g.DrawLine(Pens.White, topLeftCorner, topRightCorner);
                    g.DrawLine(Pens.White, topLeftCorner, bottomLeftCorner);
                    g.DrawLine(Pens.White, topRightCorner, bottomRightCorner);
                    g.DrawLine(Pens.White, bottomLeftCorner, bottomRightCorner);

                    outputBitmap(drawnBitmap, "Rotated box LP");
                }
            }


            //******************************************//
            //****** Crop Rotated license plates *******//
            List<VImage> CroppedStraightLPImages = new List<VImage>();

            foreach (VImage curImage in rotatedLPImages) {
                VImage croppedLPImage = curImage.BlobCrop(curImage.blob.minx, curImage.blob.miny, curImage.blob.maxx, curImage.blob.maxy);
                croppedLPImage.blob = curImage.blob;
                CroppedStraightLPImages.Add(croppedLPImage);

                if (verboseMode) {
                    outputBitmap(croppedLPImage.getBitmap(), "Cropped LP");
                }
            }

            //***************************************//
            //********* Digit Segmentation **********//
            List<VImage> Digits = new List<VImage>();
            int PossibleLPIndex = 0;
            String LPN = "";
            foreach (VImage licensePlate in CroppedStraightLPImages) {
                LPN = "";
                Digits.AddRange(NumberSegment.SegmentDigits(licensePlate));

                if (Digits.Count == 0) {
                    PossibleLPIndex++;
                    continue;
                }

                //*******************************//
                //********* OCR *****************//
                //Console.WriteLine("Candidate characters:");
                for (int i = 0; i < Digits.Count; i++) {
                    
                    Digits[i] = binarize.BinarizeImage(Digits[i]);

                    char c = OCR.DoOCR(Digits[i].getBitmap());
                    LPN += c;
                    
                    if (verboseMode) {
                        outputBitmap(Digits[i].getBitmap(), c.ToString());
                    }
                }

                Digits.Clear();
                foundLPNs.Add(LPN);
                LicensePlate FullLPFound = PossibleLicensePlates[PossibleLPIndex];

                FullLPFound.text = LPN;
                FinalLicensePlates.Add(FullLPFound);

                PossibleLPIndex++;
            }

            return FinalLicensePlates;
        }




        /* ************************************************ */
        /* ********** Output to UI (main thread) ********** */
        /* ************************************************ */
        private void log(string message) {
            backgroundWorker.ReportProgress(0, message);
        }

        private void outputBitmap(Bitmap resultBitmap, string name) {
            // create arguments list
            List<object> arguments = new List<object>();
            arguments.Add(new Bitmap(resultBitmap));
            arguments.Add(name);
            backgroundWorker.ReportProgress(1, arguments);
        }
    }
}
